#!/usr/bin/python
# -*- coding: UTF-8 -*-

'''※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※
File Name: cut_word.py
Author: GID5564
Description: 切割图像
Version: 1.0
Created Time: 24/04/24-16:08:48
※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※'''
 

import numpy as np
import cv2
from PIL import Image, ImageDraw, ImageFont
import PIL
import matplotlib.pyplot as plt
import os
import shutil
from numpy.core.records import array
from numpy.core.shape_base import block
import time



class ImageCut():
    def __init__(self,img_path):
        """
        初始化图片，通过二值化，过滤掉杂色，通过反色将黑白对调，原来白纸区域都是255，现在黑色都是0，更利于计算
        :param img_path: 图像完整路径
        """
        try:
            # 读入图片
            self.img=cv2.imread(img_path,0) 
            # 二值化并且反色
            self.binary=self.image_binary(self.img)
        except BaseException as e:
            print(e)
    
    def image_binary(self,img,thresh=200,iter=0):
        """
        图片二值化及膨胀
        :param img: 图片
        :param thresh: 切图列表
        :param iter: 膨胀系数，为0不膨胀 
        :return img_paths: 切图保存位置列表
        """
        ret,binary=cv2.threshold(img,thresh,255,cv2.THRESH_BINARY_INV) 
        if iter==0:
            return binary
        else:
            kernel=np.ones((3,3),np.uint8)
            swell=cv2.dilate(binary,kernel,iterations=6)
            return swell

            
    def save_imgs(self,dir_name,imgs):
        """
        保存图片
        :param dir_name:保存目录路径
        :param imgs: 切图列表
        :return img_paths: 切图保存位置列表
        """
        if os.path.exists(dir_name):
            shutil.rmtree(dir_name) 
        if not os.path.exists(dir_name):
            os.makedirs(dir_name)
        img_paths = []
        for i in range(0,len(imgs)):
            file_path = dir_name+'/part_'+str(i)+'.png'
            cv2.imwrite(file_path,imgs[i])
            img_paths.append(file_path)
        return img_paths
        
    def get_square_img(self,img):
        """
        获取方图
        :param img: np.ndarray图片
        :return img_large: 标准方图
        """
        #计算图片轮廓的最小矩形边界框
        x, y, w, h = cv2.boundingRect(img)
        img = img[y:y+h, x:x+w]

        max_size = 18
        max_size_and_border = 24

        if w > max_size or h > max_size: # 有超过宽高的情况
            if w>=h: # 宽比高长，压缩宽
                times = max_size/w
                w = max_size
                h = int(h*times)
            else: # 高比宽长，压缩高
                times = max_size/h
                h = max_size
                w = int(w*times)
            # 保存图片大小
            img = cv2.resize(img, (w, h))

        xw = img.shape[0]
        xh = img.shape[1]

        xwLeftNum = int((max_size_and_border-xw)/2)
        xwRightNum = (max_size_and_border-xw) - xwLeftNum

        xhLeftNum = int((max_size_and_border-xh)/2)
        xhRightNum = (max_size_and_border-xh) - xhLeftNum
    
        img_large=np.pad(img,((xwLeftNum,xwRightNum),(xhLeftNum,xhRightNum)),'constant', constant_values=(0,0)) 
    
        return img_large
        
    def cut_img(self,img, mark_boxs, is_square = False):
        """
        裁剪图片
        :param img: np.ndarray图片
        :param mark_boxs: 图片的四个顶点的坐标列表.[左，上，右，下]
        :param is_square: 是否为方图,默认False
        :return img_items: 切图列表
        """
        img_items = []
        for i in range(0,len(mark_boxs)):
            img_org = img.copy()
            box = mark_boxs[i]
            img_item = img_org[box[1]:box[3], box[0]:box[2]]

            if is_square: # 是否转化为方形
                img_item = self.get_square_img(img_item)
            img_items.append(img_item)
        return img_items
        
    def img_x_shadow(self,img):
        """
        计算X轴投影
        整幅图片的x轴投影，传入图片数组，图片经过二值化并反色
        :param img: np.ndarray图片
        :return cols: 图片每列有效像素点数列表
        """
        (h,w)=img.shape
        #初始化一个跟图像宽一样长度的数组，用于记录每一列的像素数量
        cols =[0 for z in range(0,w)]
        # 遍历每一列，记录下这一列包含多少有效像素点
        for i in range(0,w):           
            for j in range(0,h):      
                if img[j,i]==255:
                    cols[i]+=1          
        return cols
        
    def img_y_shadow(self,img):
        """
        计算Y轴投影
        整幅图片的y轴投影，传入图片数组，图片经过二值化并反色
        :param img: np.ndarray图片
        :return rows: 图片每行有效像素点数列表
        """
        (h,w)=img.shape
        # 初始化一个跟图像高一样长度的数组，用于记录每一行的黑点个数
        rows=[0 for z in range(0,h)]
        # 遍历每一行，记录下这一行包含多少有效像素点
        for i in range(0,h):          
            for j in range(0,w):      
                if img[i,j]==255:    
                    rows[i]+=1  
        return rows
        
    def row2blocks(self,cols, w, h):
        """
        根据投影切分图块
        图片获取文字块，传入x投影列表，返回标记的数组区域坐标[[左，上，右，下]]
        :param cols: 图片每列有效像素点数列表
        :param w: 图宽
        :param h: 图高
        :return block_mark_boxs: 图块的四个顶点的坐标列表.[左，上，右，下]
        """
        inLine = False # 是否已经开始切分
        start = 0 # 某次切分的起始索引
        block_mark_boxs = [] # 切分的矩形区域坐标[左，上，右，下]
        for i in range(0,len(cols)):
            # 如果还没有开始切，并且这列有效像素超过2个        
            if inLine == False and cols[i] > 2:
                inLine = True # 标记为现在开始切块
                start = i # 标记这次切块的位置索引
            # 如果在切，并且已经超过10个，并且这次低于2个有效像素，说明遇到空白了
            elif i-start >10 and cols[i] < 2 and inLine: 
                inLine = False # 标记不切了
                # 记录这次选中的区域[左，上，右，下]，上下就是图片，左右是start到当前
                left = max(start-1, 0)
                right = min(w, i+1)
                box = [left, 0, right, h]
                block_mark_boxs.append(box)  

        return block_mark_boxs
        
    def img2rows(self,rows,w,h):
        """
        根据投影切分图块
        图片获取文字块，传入y投影列表，返回标记的数组区域坐标[[左，上，右，下]]
        :param rows: 图片每行有效像素点数列表
        :param w: 图宽
        :param h: 图高
        :return mark_boxs: 图块的四个顶点的坐标列表.[左，上，右，下]
        """
        ### 根据投影切分图块 ### 
        inLine = False # 是否已经开始切分
        start = 0 # 某次切分的起始索引
        mark_boxs = []
        for i in range(0,len(rows)):    
            if inLine == False and rows[i] > 10:
                inLine = True
                start = i
            # 记录这次选中的区域[左，上，右，下]，上下就是图片，左右是start到当前
            elif i-start >5 and rows[i] < 10 and inLine:
                inLine = False
                if i-start > 10:
                    top = max(start-1, 0)
                    bottom = min(h, i+1)
                    box = [0, top, w, bottom]
                    mark_boxs.append(box)
        return mark_boxs

    def block2chars(self,cols, w, h,row_top,block_left):
        """
        根据投影切分图块
        图片获取文字块，传入x投影列表，返回标记的数组区域坐标[[左，上，右，下]]
        :param cols: 图片每列有效像素点数列表
        :param w: 图宽
        :param h: 图高
        :return char_mark_boxs，
                abs_char_mark_boxs:切分的矩形区域坐标[左，上，右，下]
        """

        ### 根据投影切分图块 ### 
        inLine = False # 是否已经开始切分
        start = 0 # 某次切分的起始索引
        char_mark_boxs = [] # 切分的矩形区域坐标[左，上，右，下]
        abs_char_mark_boxs = [] # 切分的矩形区域坐标[左，上，右，下]

        for i in range(0,len(cols)):
            # 如果还没有开始切，并且这列有效像素超过1个        
            if inLine == False and cols[i] > 0:
                inLine = True # 标记为现在开始切块
                start = i # 标记这次切块的位置索引
            # 如果在切，并且已经超过5个，并且这次低于2个有效像素，说明遇到空白了
            elif i-start >5 and cols[i] < 1 and inLine: 
                inLine = False # 标记不切了
                # 记录这次选中的区域[左，上，右，下]，上下就是图片，左右是start到当前
                left = max(start-1, 0)
                right = min(w, i+1)
                box = [left, 0, right, h]
                char_mark_boxs.append(box)
                ads_box = [block_left+left, row_top,block_left+right, row_top+h]  
                abs_char_mark_boxs.append(ads_box)  

        return char_mark_boxs,abs_char_mark_boxs
        
    def divImg(self,img_path, save_file = False):
        """
        分割图像
        :param img_path: 图片地址
        :param save_file: 是否要保存切图
        :return all_mark_boxs: 切分的矩形区域坐标[左，上，右，下]
                all_char_imgs: 字符切图

        """
        img=self.img
        (img_h,img_w)=img.shape
        img_b=self.binary
        
        # 计算投影，并截取整个图片的行
        img_y_shadow_a = self.img_y_shadow(img_b)
        row_mark_boxs = self.img2rows(img_y_shadow_a,img_w,img_h)
        # 切行的图片，切的是原图
        row_imgs = self.cut_img(img, row_mark_boxs)
        all_mark_boxs = []
        all_char_imgs = []
        # =====从行切块=======
        for i in range(0,len(row_imgs)):
            row_img = row_imgs[i]
            (row_img_h,row_img_w)=row_img.shape
            
            
            # 二值化一行的图，用于切块
            #图像膨胀6次
            row_img_b_d=self.image_binary(row_img,iter=6)

            
            img_x_shadow_a = self.img_x_shadow(row_img_b_d)
            block_mark_boxs = self.row2blocks(img_x_shadow_a, row_img_w, row_img_h)
            row_char_boxs = []
            row_char_imgs = []
            # 切块的图，切的是原图
            block_imgs = self.cut_img(row_img, block_mark_boxs)
            if save_file:
                #保存行块
                b_imgs = self.save_imgs('imgs/cuts/row_'+str(i), block_imgs) # 如果要保存切图
            # ======从块切字=========
            for j in range(0,len(block_imgs)):
                block_img = block_imgs[j]
                (block_img_h,block_img_w)=block_img.shape
                
                
                # 二值化块,因为要切字符图片了
                block_img_b=self.image_binary(block_img)

                
                block_img_x_shadow_a = self.img_x_shadow(block_img_b)
                row_top = row_mark_boxs[i][1]
                block_left = block_mark_boxs[j][0]
                char_mark_boxs,abs_char_mark_boxs = self.block2chars(block_img_x_shadow_a, block_img_w, block_img_h,row_top,block_left)
                row_char_boxs.append(abs_char_mark_boxs)
                # 切的是二值化的图
                char_imgs = self.cut_img(block_img_b, char_mark_boxs, True)
                row_char_imgs.append(char_imgs)
                if save_file:
                    #保存单字符块
                    c_imgs = self.save_imgs('imgs/cuts/row_'+str(i)+'/blocks_'+str(j), char_imgs) # 如果要保存切图
            all_mark_boxs.append(row_char_boxs)
            all_char_imgs.append(row_char_imgs)

        return all_mark_boxs,all_char_imgs


if __name__ == "__main__":
    img_path="img.png"

    ic=ImageCut(img_path)
    ic.divImg(img_path,1)
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    